#ifndef SIMPLE_SUPPORT_ITERATOR_OUT_PARTITION_HPP
#define SIMPLE_SUPPORT_ITERATOR_OUT_PARTITION_HPP

#include <iterator> // std::iterator_traits std::output_iterator_tag
#include <algorithm> // std::iter_swap
#include <utility> // std::forward std::move

#include "../function_utils.hpp" // support::invoke

namespace simple::support
{

	// TODO: disallow nesting with iterators that don't make sense
	// ostream - nope
	// filter - nope
	// flatten_tuple - nope, but theoretically possible with fixed size
	// flatten_range - nope
	// invoke - nope
	// optional - nope
	// parition - nope
	// accumulate - nope
	// transform - yep
	// offset/expander - yep
	template <typename It, typename PartitionPoint, typename Predicate>
	struct out_partition
	{
		using value_type = typename std::iterator_traits<It>::value_type;
		using difference_type = typename std::iterator_traits<It>::difference_type;
		using pointer = typename std::iterator_traits<It>::pointer;
		using reference = typename std::iterator_traits<It>::reference;
		using iterator_category = std::output_iterator_tag;

		It out;
		PartitionPoint partition_point;
		Predicate predicate;

		template <typename P>
		out_partition(P&& predicate, PartitionPoint partition_point, It out) :
			out(std::move(out)),
			partition_point(std::move(partition_point)),
			predicate(std::forward<P>(predicate))
		{}

		out_partition& operator++() { ++out; return *this; }
		out_partition operator++(int) { out_partition prev = *this; ++(*this); return prev; }
		out_partition& operator--() { --out; return *this; }
		out_partition operator--(int) { out_partition prev = *this; --(*this); return prev; }

		class proxy
		{
			It& out;
			PartitionPoint& partition_point;
			Predicate& predicate;

			proxy(It& out, PartitionPoint& partition_point, Predicate& predicate)
				: out(out), partition_point(partition_point), predicate(predicate) {}
			friend struct out_partition;
			public:
			proxy() = delete;
			proxy(const proxy&) = delete;
			proxy(const proxy&&) = delete;
			proxy& operator=(const proxy&) = delete;
			proxy& operator=(const proxy&&) = delete;

			template <typename T>
			const proxy& operator=(T&& x) const
			{
				auto condition = support::invoke(predicate, x);
				*out = std::forward<T>(x); // quickly put the thing there
				if(condition)
				{
					// ok now that it's there safe and sound can shuffle it around
					std::iter_swap(partition_point, out);
					// TODO: out_stable_partiton
					// std::rotate(partition_point, out, std::next(out));
					++partition_point;
				}
				// why not
				// if(predicate(x))
				//    swap(x, *partition_point++);
				// *out = x;
				// cause the partition point might not be swappable with x,
				// since underlying out iterator may transform - can't
				// untransform what is transformed already, but can swap two
				// transformed things theoretically
				return *this;
			}

		};

		const proxy operator*()
		{
			return {out, partition_point, predicate};
		}
	};
	template <typename I, typename PP, typename P> out_partition(P&&, PP, I)
		-> out_partition<I, PP, P>;

} // namespace simple::support

#endif /* end of include guard */
